{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[官方手册](https://github.com/andymccurdy/redis-py/blob/master/README.rst)\n",
    "# 安装\n",
    "\n",
    "## 源码安装最新版redis-py 3.x（推荐）\n",
    "\n",
    "- 下载地址 https://github.com/andymccurdy/redis-py/releases\n",
    "- 解压，cd\n",
    "- python setup.py install\n",
    "\n",
    "## 或者 pip install redis \n",
    "安装完成后检查版本\n",
    ">pip show redis\n",
    "\n",
    "redis 2.x 和3.x 参数差异较多，使用2.x时，根据下文介绍注意区别"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Getting Started "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import redis"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 查看版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3, 2, 1)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "redis.VERSION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# r = redis.Redis(host='47.102.127.104', port=6379, db=0)\n",
    "r = redis.Redis(host='localhost', port=6379, db=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r.set('foo', 'bar')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "b'bar'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r.get('foo')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# redis-py 2.X 与 3.0 使用区别\n",
    "\n",
    "## Redis and StrictRedis\n",
    "\n",
    "1. “StrictRedis”已重命名为“Redis”，并且提供了名为“StrictRedis”的别名，以便之前使用“StrictRedis”的用户可以继续保持不变。\n",
    "\n",
    "   已经使用StrictRedis的2.X用户不必更改类名。\n",
    "2. 以下命令稍作调整(以下为redis-py 3.x)\n",
    " - SETEX: 参数顺序 (name, time, value).\n",
    " - LREM: 参数顺序 (name, num, value).\n",
    " - TTL and PTTL: 返回值现在始终为int并且与官方Redis命令匹配\n",
    "     （> 0表示超时，-1表示密钥存在但是没有设置过期时间，-2表示密钥不存在）\n",
    "     \n",
    "## SSL Connections    \n",
    "redis-py 3.0将`ssl_cert_reqs`选项的默认值从None更改为'required'。\n",
    "\n",
    "此更改在从远程SSL终结器接受证书时强制执行主机名验证。如果终结器没有在cert上正确设置主机名，这将导致redis-py 3.0引发`ConnectionError`。可以通过将**ssl_cert_reqs设置为None**来禁用此检查。请注意，这样做会删除安全检查。这样做自担风险。\n",
    "\n",
    "## MSET, MSETNX and ZADD参数改为字典\n",
    "\n",
    "现在3.0 的参数顺序是：\n",
    "```python\n",
    "def mset(self, mapping):\n",
    "def msetnx(self, mapping):\n",
    "def zadd(self, name, mapping, nx=False, xx=False, ch=False, incr=False):\n",
    "```\n",
    "\n",
    "## ZINCRBY\n",
    "现在3.0 的参数顺序是：\n",
    "```python\n",
    "def zincrby(self, name, amount, value):\n",
    "```\n",
    "\n",
    "## 编码和输出\n",
    "\n",
    "redis-py 3.0仅接受用户数据作为字节，字符串或数字（整数，长整数和浮点数），尝试将键或值指定为任何其他类型将引发DataError异常。\n",
    "\n",
    "redis-py 2.X试图将任何类型的输入强制转换为字符串。虽然偶尔方便，当用户传递布尔值（强制为'True'或'False'），None值（强制为'None'）或其他值（如用户定义）时，会导致各种隐藏错误类型。\n",
    "\n",
    "所有2.X用户都应该确保它们传递给redis-py的键和值是字节，字符串或数字。\n",
    "\n",
    "## Locks\n",
    "\n",
    "redis-py 3.0支持基于管道的Lock，现在只支持基于Lua的锁。在这样做时，LuaLock已重命名为Lock。这也意味着redis-py Lock对象需要Redis服务器2.6或更高版本。“LuaLock”的2.X用户现在必须改为使用“Lock”。\n",
    "\n",
    "## Locks as Context Managers上下文管理器\n",
    "\n",
    "- redis-py 3.0现在在使用锁作为上下文管理器时引发LockError，并且无法在指定的超时内获取锁。\n",
    "\n",
    "- 2.X用户应确保将他们的锁码包装在try / catch中，如下所示：\n",
    "```python\n",
    "try:\n",
    "    with r.lock('my-lock-key', blocking_timeout=5) as lock:\n",
    "        # code you want executed only after the lock has been acquired\n",
    "except LockError:\n",
    "    # the lock wasn't acquired\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# API 参考\n",
    "\n",
    "[Redis官方命令文档](https://redis.io/commands)\n",
    "\n",
    "redis-py试图遵循官方命令语法，除了如下例外：\n",
    "\n",
    "- SELECT: 未实现。请参阅下面“线程安全”部分中的说明\n",
    "\n",
    "- DEL: 'del'是Python语法中的保留关键字。因此redis-py使用'delete'代替。\n",
    "\n",
    "- MULTI/EXEC: 作为Pipeline类的一部分实现的。默认情况下，**管道在执行时用MULTI和EXEC语句包装，可以通过指定transaction = False来禁用**。查看以下有关管道的更多信息。\n",
    "\n",
    "- SUBSCRIBE/LISTEN: 与管道类似，PubSub实现为一个单独的类，因为它将底层连接置于无法执行非pubsub命令的状态。从Redis客户端调用pubsub方法将返回一个PubSub实例，可以在其中订阅频道并侦听消息。只能从Redis客户端调用PUBLISH\n",
    "\n",
    "- SCAN/SSCAN/HSCAN/ZSCAN: 每个命令都有一个等效的迭代器方法。对此使用scan_iter / sscan_iter / hscan_iter / zscan_iter方法。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 更多细节\n",
    "\n",
    "## 连接池\n",
    "\n",
    "redis-py使用连接池来管理与Redis服务器的连接。默认情况下，您创建的每个Redis实例将依次创建自己的连接池。您可以通过将已创建的连接池实例传递给Redis类的connection_pool参数来覆盖此行为并使用现有连接池。这样就可以实现多个Redis实例共享一个连接池。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "pool = redis.ConnectionPool(host='localhost', port=6379, db=0)\n",
    "r = redis.Redis(connection_pool=pool)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Connections\n",
    "\n",
    "redis-py有两种类型的连接。默认值Connection是基于TCP套接字的常规连接。UnixDomainSocketConnection允许在与服务器相同的设备上运行的客户端通过unix域套接字进行连接。要使用UnixDomainSocketConnection连接，只需将unix_socket_path参数传递给unix域套接字文件，该参数是一个字符串。此外，请确保在redis.conf文件中定义了unixsocket参数，它默认注释掉了。\n",
    "> r = redis.Redis(unix_socket_path='/tmp/redis.sock')\n",
    "\n",
    "您也可以创建自己的Connection子类。如果要在异步框架内控制套接字行为，这可能很有用。要使用您自己的连接实例化客户端类，您需要创建一个连接池，将您的类传递给connection_class参数。传递给池的其他关键字参数将传递给初始化期间指定的类。\n",
    ">pool = redis.ConnectionPool(connection_class=YourConnectionClass,\n",
    "                                your_arg='...', ...)\n",
    "\n",
    "## Parsers\n",
    "\n",
    "解析器解析器类提供了一种控制如何解析Redis服务器响应的方法。redis-py附带了两个解析器类，PythonParser和HiredisParser。\n",
    "\n",
    "默认情况下，如果安装了hiredis模块，redis-py将尝试使用HiredisParser，否则将回退到PythonParser。\n",
    "\n",
    "Hiredis是由Redis核心团队维护的C库。Pieter Noordhuis非常友好地创建了Python绑定。使用Hiredis可以在解析Redis服务器的响应时提供高达**10倍的速度提升**。在检索许多数据时，例如来自LRANGE或SMEMBERS操作，性能提升最为明显。Hiredis可以在PyPI上使用，并且可以像redis-py一样通过pip安装。\n",
    "\n",
    "> $ pip install hiredis\n",
    "\n",
    "\n",
    "## 回调函数\n",
    "\n",
    "客户端类使用一组回调将Redis响应强制转换为适当的Python类型。Redis客户端类在名为`RESPONSE_CALLBACKS`的字典中定义了许多这些回调。可以使用`set_response_callback`方法基于每个实例添加自定义回调。此方法接受两个参数：命令名称和回调。以这种方式添加的回调仅在添加回调的实例上有效。如果要**全局定义或覆盖回调，则应创建Redis客户端的子类**，并将回调添加到其`RESPONSE_CALLBACKS类字典`中。\n",
    "\n",
    "响应回调至少需要一个参数：来自Redis服务器的响应。也可以接受关键字参数，以便进一步控制如何解释响应。这些关键字参数是在命令调用`execute_command`期间指定的。ZRANGE实现演示了使用响应回调关键字参数及其“withscores”参数\n",
    "\n",
    "## 线程安全\n",
    "\n",
    "可以在线程之间安全地共享Redis客户端实例。在内部，连接实例仅在命令执行期间从连接池中检索，并在之后直接返回到池。命令执行永远不会修改客户端实例上的状态。\n",
    "\n",
    "但是，有一点需要注意：`Redis SELECT命令`，SELECT命令允许您切换连接当前使用的数据库。该数据库保持选中状态，直到选择另一个数据库或直到连接关闭，这会产生一个问题，即连接可以返回到连接到不同数据库的池。\n",
    "\n",
    "因此，redis-py不会在客户端实例上实现`SELECT命令`。**如果在同一应用程序中使用多个Redis数据库，则应为每个数据库创建单独的客户端实例**（可能还有单独的连接池）。\n",
    "\n",
    "在线程之间传递PubSub或Pipeline对象是不安全的。\n",
    "\n",
    "## 管道：\n",
    "\n",
    "管道是Redis类的子类，它为在单个请求中**向服务器缓冲多个命令**提供支持。它们可用于通过减少客户端和服务器之间来回TCP数据包的数量来显着提高**命令组**的性能。\n",
    "\n",
    "```python\n",
    ">>> r = redis.Redis(...)\n",
    ">>> r.set('bing', 'baz')\n",
    ">>> # 使用pipeline()方法 创建 pipeline 实例\n",
    ">>> pipe = r.pipeline()\n",
    ">>> # 下面是 SET、 GET 缓冲命令\n",
    ">>> pipe.set('foo', 'bar')\n",
    ">>> pipe.get('bing')\n",
    ">>> # 发送所有缓冲命令到服务器, 返回一个列表\n",
    ">>> # 对应每条命令的相应\n",
    ">>> pipe.execute()\n",
    "[True, 'baz']\n",
    "```\n",
    "\n",
    "为了便于使用，可以简化用法：\n",
    "```python\n",
    ">>> pipe.set('foo', 'bar').sadd('faz', 'baz').incr('auto_number').execute()\n",
    "[True, True, 6]\n",
    "```\n",
    "\n",
    "此外，管道还可以确保缓冲的命令作为一个组**原子**地执行，这是默认的。\n",
    "如果要禁用管道的原子性质但仍希望缓冲命令，则可以关闭此规则。\n",
    "```\n",
    ">>> pipe = r.pipeline(transaction=False)\n",
    "```\n",
    "---\n",
    "\n",
    "当需要原子事务但需要先在Redis中检索值以便在事务中使用时，会出现一个常见问题。\n",
    "\n",
    "例如，假设INCR命令不存在，我们需要在Python中构建INCR的命令的本质代码。\n",
    "\n",
    "在python代码中获取值，并且使其增加一个值，表面上很简单，但是，这个过程不是原子的。因为多个客户端可以同时执行此操作，在此期间每个客户端都可以GET获取相同的值。\n",
    "\n",
    "`WATCH命令`：WATCH提供在事务开始之前监视一个或多个key的能力。如果在执行该事务之前这些key中的任何一个发生了更改，则将取消整个事务并引发WatchError。\n",
    "\n",
    "现在我们在遵循原子性的基础上**手动实现客户端INCR命令**，我们可以这样做：\n",
    "\n",
    "- 方法一：在with语句块内方式：\n",
    "```python\n",
    ">>> with r.pipeline() as pipe:\n",
    "...     while True:\n",
    "...         try:\n",
    "...             # 用 WATCH 监视并保持一个 key\n",
    "...             pipe.watch('OUR-SEQUENCE-KEY')\n",
    "...             # 在 WATCHing之后,管道进入立即执行模式，直到我们告诉它再次开始缓冲命令。\n",
    "               # 这允许我们获得序列的当前值\n",
    "...             current_value = pipe.get('OUR-SEQUENCE-KEY')\n",
    "...             next_value = int(current_value) + 1\n",
    "...             # 现在我们可以使用MULTI将管道重新置于缓冲模式\n",
    "...             pipe.multi()\n",
    "...             pipe.set('OUR-SEQUENCE-KEY', next_value)\n",
    "...             # 最后，执行管道（set命令）\n",
    "...             pipe.execute()\n",
    "...             # 如果没有发生WatchError 异常，则以上操作是遵循原子性的事务\n",
    "...             break\n",
    "...        except WatchError:\n",
    "...            # 从我们监视'OUR-SEQUENCE-KEY'开始后，另一个客户更改了'OUR-SEQUENCE-KEY'的值。\n",
    "               # 我们最好的选择是进行不断重试，直到实现INCR功能。\n",
    "...             continue\n",
    "```\n",
    "\n",
    "请注意，由于**Pipeline必须在WATCH期间绑定到单个连接**，因此必须注意确保通过调用reset（）方法将连接返回到连接池。**如果Pipeline用作上下文管理器（如上例所示with语句块），将自动调用reset（）**。当然，您可以通过显式调用reset（）以手动方式执行此操作：\n",
    "\n",
    "- 方法二：不用with语句块：\n",
    "```python\n",
    ">>> pipe = r.pipeline()\n",
    ">>> while True:\n",
    "...     try:\n",
    "...         pipe.watch('OUR-SEQUENCE-KEY')\n",
    "...         ...\n",
    "...         pipe.execute()\n",
    "...         break\n",
    "...     except WatchError:\n",
    "...         continue\n",
    "...     finally:\n",
    "...         pipe.reset()\n",
    "```\n",
    "- 方法三：\n",
    "存在一种名为“transaction”的便捷方法，它需要一个可调用的参数（函数参数），一个管道对象和任何数量被WATCHed的key。我们上面的客户端INCR命令可以像这样编写，更容易阅读：\n",
    "\n",
    "```python\n",
    ">>> def client_side_incr(pipe):\n",
    "...     current_value = pipe.get('OUR-SEQUENCE-KEY')\n",
    "...     next_value = int(current_value) + 1\n",
    "...     pipe.multi()\n",
    "...     pipe.set('OUR-SEQUENCE-KEY', next_value)\n",
    ">>>\n",
    ">>> r.transaction(client_side_incr, 'OUR-SEQUENCE-KEY')\n",
    "[True]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 发布/订阅\n",
    "\n",
    "redis-py包含一个PubSub对象，该对象订阅频道并侦听新消息。创建PubSub对象很简单。\n",
    "```python\n",
    ">>> r = redis.Redis(...)\n",
    ">>> p = r.pubsub()\n",
    "```\n",
    "\n",
    "一旦创建了PubSub实例，就可以订阅一个或多个频道了。\n",
    "```python\n",
    ">>> p.subscribe('my-first-channel', 'my-second-channel', ...)\n",
    ">>> p.psubscribe('my-*', ...)\n",
    "```\n",
    "\n",
    "PubSub实例现在订阅了这些频道/pattern。通过从PubSub实例读取消息可以看到订阅确认。\n",
    "\n",
    "```python\n",
    ">>> p.get_message()\n",
    "{'pattern': None, 'type': 'subscribe', 'channel': 'my-second-channel', 'data': 1L}\n",
    ">>> p.get_message()\n",
    "{'pattern': None, 'type': 'subscribe', 'channel': 'my-first-channel', 'data': 2L}\n",
    ">>> p.get_message()\n",
    "{'pattern': None, 'type': 'psubscribe', 'channel': 'my-*', 'data': 3L}\n",
    "```\n",
    "\n",
    "从PubSub实例读取的每条消息都是具有以下key的字典。\n",
    "\n",
    "- type：以下之一：'subscribe'，'unsubscribe'，'psubscribe'，'punsubscribe'，'message'，'pmessage'\n",
    "\n",
    "- channel：订阅\\取消订阅的频道，或者published发布消息的频道\n",
    "\n",
    "- pattern：匹配已发布消息的通道的模式。除了'pmessage'类型之外，在所有情况下都将转为None。\n",
    "\n",
    "- data：消息数据。使用[un]subscribe 消息，此值将是连接当前订阅的通道和模式的数量。使用[p]message消息，此值将是实际发布的消息。\n",
    "\n",
    "发布消息\n",
    "\n",
    "```python\n",
    "# publish方法返回匹配频道和pattern的订阅数。\n",
    "# `my-first-channel`匹配`my-first-channel`订阅和`my- *`模式订阅，\n",
    "# 所以此消息将返回到2个订阅数\n",
    ">>> r.publish('my-first-channel', 'some data')\n",
    "2\n",
    "\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-first-channel', 'data': 'some data', 'pattern': None, 'type': 'message'}\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-first-channel', 'data': 'some data', 'pattern': 'my-*', 'type': 'pmessage'}\n",
    "```\n",
    "取消订阅就像订阅一样。如果没有参数传递给[p]unsubscribe，则所有渠道或pattern都将取消订阅。\n",
    "\n",
    "```python\n",
    ">>> p.unsubscribe()\n",
    ">>> p.punsubscribe('my-*')\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-second-channel', 'data': 2L, 'pattern': None, 'type': 'unsubscribe'}\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-first-channel', 'data': 1L, 'pattern': None, 'type': 'unsubscribe'}\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-*', 'data': 0L, 'pattern': None, 'type': 'punsubscribe'}\n",
    "```\n",
    "\n",
    "redis-py还允许您注册**回调函数**来处理已**发布**的消息。\n",
    "\n",
    "消息handler传入单个参数，即消息message，这是一个字典，就像上面的例子一样。\n",
    "\n",
    "要使用消息handler订阅通道或模式，请将通道或模式名称作为关键字参数传递，其值为回调函数。\n",
    "\n",
    "当使用消息handler在通道或模式上读取消息时，将创建消息字典并将其传递给消息处理程序。在这种情况下，由于消息已经处理，因此从`get_message()`返回None值\n",
    "\n",
    "```python\n",
    ">>> def my_handler(message):\n",
    "...     print('MY HANDLER: ', message['data'])\n",
    ">>> p.subscribe(**{'my-channel': my_handler})\n",
    "# read the subscribe confirmation message\n",
    ">>> p.get_message()\n",
    "{'pattern': None, 'type': 'subscribe', 'channel': 'my-channel', 'data': 1L}\n",
    ">>> r.publish('my-channel', 'awesome data')\n",
    "1\n",
    "\n",
    ">>> message = p.get_message()\n",
    "MY HANDLER:  awesome data\n",
    "\n",
    ">>> print(message)\n",
    "None\n",
    "```\n",
    "\n",
    "如果您的应用程序对（有时是嘈杂的）订阅/取消订阅确认消息不感兴趣，可以通过将`ignore_subscribe_messages = True`传递给`r.pubsub()`来忽略它们。这将导致读取所有订阅/取消订阅消息，但它们不会影响到您的应用程序。\n",
    "\n",
    "```python\n",
    ">>> p = r.pubsub(ignore_subscribe_messages=True)\n",
    ">>> p.subscribe('my-channel')\n",
    ">>> p.get_message()  # 隐藏订阅消息，并且返回None\n",
    ">>> r.publish('my-channel', 'my data')\n",
    "1\n",
    ">>> p.get_message()\n",
    "{'channel': 'my-channel', 'data': 'my data', 'pattern': None, 'type': 'message'}\n",
    "```\n",
    "\n",
    "**阅读消息有三种不同的策略。**\n",
    "\n",
    "- 方法一：\n",
    "上面的示例一直使用`pubsub.get_message()`，在内部，`get_message()`使用系统的“select”模块快速轮询连接的套接字。如果有可供读取的数据，`get_message()`将读取它，格式化消息并将其返回或传递给消息处理程序。如果没有要读取的数据，`get_message()`将立即返回`None`。\n",
    "\n",
    "```python\n",
    ">>> while True:\n",
    ">>>     message = p.get_message()\n",
    ">>>     if message:\n",
    ">>>         # do something with the message\n",
    ">>>     time.sleep(0.001)  # be nice to the system :)\n",
    "```\n",
    "方法二：\n",
    "较早版本的redis-py只能使用`pubsub.listen()`读取消息。`listen()`是一个阻塞直到消息可用的生成器。如果您的应用程序不需要执行任何其他操作，只需接收和处理从redis收到的消息，`listen()`是一种简单的方法来启动运行。\n",
    "```python\n",
    ">>> for message in p.listen():\n",
    "...     # do something with the message\n",
    "```\n",
    "方法三：\n",
    "第三个选项在单独的线程中运行事件循环。`pubsub.run_in_thread()`创建一个新线程并启动事件循环。线程对象返回给`run_in_thread()`的调用者。调用者可以使用`thread.stop()`方法来关闭事件循环和线程。在幕后，这只是一个围绕`get_message()`的包装器，它在一个单独的线程中运行，实际上为你创建了一个微小的非阻塞事件循环。`run_in_thread()`接受一个可选的`sleep_time`参数。如果指定，则事件循环将使用循环的每次迭代中的值调用`time.sleep()`。\n",
    "\n",
    "注意：由于我们在单独的线程中运行，因此无法处理未使用已注册的消息处理程序自动处理的消息。因此，如果您订阅了没有附加消息处理程序的模式或通道，redis-py会阻止您调用`run_in_thread()`。\n",
    "\n",
    "```python\n",
    ">>> p.subscribe(**{'my-channel': my_handler})\n",
    ">>> thread = p.run_in_thread(sleep_time=0.001)\n",
    "# the event loop is now running in the background processing messages\n",
    "# when it's time to shut it down...\n",
    ">>> thread.stop()\n",
    "```\n",
    "\n",
    "PubSub对象遵循与创建它的客户端实例相同的编码语义。在发送到Redis之前，将使用客户端上指定的字符集对任何unicode的通道或模式进行编码。如果客户端的`decode_responses`标志设置为False（默认值），则消息字典中的`channel`，`pattern`和`data`值将为字节字符串（Python 2上的str，Python 3上的字节）。如果客户端的`decode_responses`为`True`，则`channel`，`pattern`和`data`值将使用客户端的字符集自动解码为unicode字符串。\n",
    "\n",
    "PubSub对象记住了他们订阅的频道和模式。如果发生网络错误或超时等断开连接，PubSub对象将在重新连接时重新订阅所有先前的通道和模式。客户端断开连接时发布的消息无法传递。完成PubSub对象后，调用其`.close()`方法关闭连接。\n",
    "\n",
    "```python\n",
    ">>> p = r.pubsub()\n",
    ">>> ...\n",
    ">>> p.close()\n",
    "```\n",
    "\n",
    "还支持PUBSUB子命令集`CHANNELS，NUMSUB和NUMPAT`：\n",
    "\n",
    "```\n",
    ">>> r.pubsub_channels()\n",
    "['foo', 'bar']\n",
    ">>> r.pubsub_numsub('foo', 'bar')\n",
    "[('foo', 9001), ('bar', 42)]\n",
    ">>> r.pubsub_numsub('baz')\n",
    "[('baz', 0)]\n",
    ">>> r.pubsub_numpat()\n",
    "1204\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Monitor\n",
    "\n",
    "redis-py包含一个Monitor对象，该对象流式传输Redis服务器处理的每个命令。在Monitor对象上使用`listen()`来阻止，直到收到命令。\n",
    "\n",
    "```python\n",
    ">>> r = redis.Redis(...)\n",
    ">>> with r.monitor() as m:\n",
    ">>>     for command in m.listen():\n",
    ">>>         print(command)\n",
    "```\n",
    "\n",
    "## Lua Scripting\n",
    "\n",
    "**略**\n",
    "\n",
    "## Sentinel support\n",
    "\n",
    "redis-py可以与[Redis Sentinel](https://redis.io/topics/sentinel)一起使用来发现Redis节点。您需要至少运行一个Sentinel守护程序才能使用redis-py的Sentinel支持。\n",
    "\n",
    "将redis-py连接到Sentinel实例很容易。您可以使用Sentinel连接来发现主站和从站网络地址：\n",
    "\n",
    "```python\n",
    ">>> from redis.sentinel import Sentinel\n",
    ">>> sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)\n",
    ">>> sentinel.discover_master('mymaster')\n",
    "('127.0.0.1', 6379)\n",
    ">>> sentinel.discover_slaves('mymaster')\n",
    "[('127.0.0.1', 6380)]\n",
    "```\n",
    "您还可以从Sentinel实例创建Redis客户端连接。您可以连接到主设备（用于写入操作）或从设备（用于只读操作）。\n",
    "```python\n",
    ">>> master = sentinel.master_for('mymaster', socket_timeout=0.1)\n",
    ">>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1)\n",
    ">>> master.set('foo', 'bar')\n",
    ">>> slave.get('foo')\n",
    "'bar'\n",
    "```\n",
    "主从对象是普通的Redis实例，其连接池绑定到Sentinel实例。当Sentinel支持的客户端尝试建立连接时，它首先查询Sentinel服务器以确定要连接的适当主机。如果未找到服务器，则引发`MasterNotFoundError`或`SlaveNotFoundError`。\n",
    "\n",
    "这两个异常都是`ConnectionError`的子类。尝试连接到从属客户端时，Sentinel连接池将遍历从属列表，直到找到可以连接的对象。如果无法连接任何从站，则将与主站建立连接。\n",
    "\n",
    "有关Redis Sentinel的详细信息，请参阅[Redis Sentinel的Redis客户端指南](https://redis.io/topics/sentinel-clients)。\n",
    "\n",
    "## Scan Iterators\n",
    "\n",
    "Redis 2.8中引入的* SCAN命令使用起来很麻烦。虽然完全支持这些命令，但redis-py还公开了以下返回Python迭代器的方法：scan_iter，hscan_iter，sscan_iter和zscan_iter。\n",
    "\n",
    "```python\n",
    ">>> for key, value in (('A', '1'), ('B', '2'), ('C', '3')):\n",
    "...     r.set(key, value)\n",
    ">>> for key in r.scan_iter():\n",
    "...     print key, r.get(key)\n",
    "A 1\n",
    "B 2\n",
    "C 3\n",
    "```\n",
    "\n",
    "## Author\n",
    "redis-py is developed and maintained by Andy McCurdy (sedrik@gmail.com). It can be found here: https://github.com/andymccurdy/redis-py\n",
    "\n",
    "Special thanks to:\n",
    "\n",
    "- Ludovico Magnocavallo, author of the original Python Redis client, from which some of the socket code is still used.\n",
    "- Alexander Solovyov for ideas on the generic response callback system.\n",
    "- Paul Hubbard for initial packaging support.\n",
    "\n",
    "## 翻译：by wanzheng"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
